using EFCoreEnterpriseDemo.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace EFCoreEnterpriseDemo.Builders
{
    public class QueryBuilder<T> where T : class
    {
        private IQueryable<T> _query;
        private List<Expression<Func<T, bool>>> _filters = new();
        private bool _includeDeleted = false;

        public QueryBuilder(DbContext context)
        {
            _query = context.Set<T>();
        }

        public QueryBuilder<T> IncludeDeleted(bool include = true)
        {
            _includeDeleted = include;
            return this;
        }

        public QueryBuilder<T> Where(Expression<Func<T, bool>> predicate)
        {
            _filters.Add(predicate);
            return this;
        }

        public QueryBuilder<T> WhereIf(bool condition, Expression<Func<T, bool>> predicate)
        {
            if (condition) _filters.Add(predicate);
            return this;
        }

        public QueryBuilder<T> Search(string keyword, params Expression<Func<T, string>>[] properties)
        {
            if (!string.IsNullOrEmpty(keyword))
            {
                var parameter = Expression.Parameter(typeof(T), "x");
                var keywordExpr = Expression.Constant(keyword.ToLower());

                Expression? body = null;
                foreach (var property in properties)
                {
                    var propertyAccess = Expression.Invoke(property, parameter);
                    var toLower = Expression.Call(propertyAccess, "ToLower", Type.EmptyTypes);
                    var contains = Expression.Call(toLower, "Contains", Type.EmptyTypes, keywordExpr);

                    body = body == null ? contains : Expression.OrElse(body, contains);
                }
                if (body != null)
                {
                    var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
                    _filters.Add(lambda);
                }
            }
            return this;
        }

        public QueryBuilder<T> OrderBy<TKey>(Expression<Func<T, TKey>> keySelector, bool descending = false)
        {
            _query = descending ?
                ((IOrderedQueryable<T>)_query).OrderByDescending(keySelector) :
                ((IOrderedQueryable<T>)_query).OrderBy(keySelector);
            return this;
        }

        public IQueryable<T> Build()
        {
            var query = _query;

            if (_includeDeleted && typeof(ISoftDelete).IsAssignableFrom(typeof(T)))
            {
                query = query.IgnoreQueryFilters();
            }

            foreach (var filter in _filters)
            {
                query = query.Where(filter);
            }

            return query;
        }
    }
}